﻿using System.Collections.Generic;
using UnityEngine;

public enum XAudioUseType
{
    PlayerSound,
    EnemiesSound,
    WorldSound,
    Sound2D,
    Music,
}

[System.Serializable]
public class XAudioPlayData
{
    public AudioClip clip;
    public Vector3 position = Vector3.zero;

    public float pitchMin = 1.0f;
    public float pitchMax = 1.0f;
    public float volume = 0.5f;
    public bool loop = false;
}

public class XAudioManager : MonoBehaviour
{
    private static XAudioManager  instance;

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public static XAudioManager GetSingleton()
    {
        return instance;
    }

    public AudioListener listener;
    public Transform target;

    [SerializeField]
    AudioClip[] defaultPickupSounds;

    [SerializeField]
    AudioClip[] defaultDropSounds;

    [SerializeField]
    AudioClip[] defaultMonsterDeathSounds;

    [SerializeField]
    AudioClip[] defaultBossDeathSounds;

    [SerializeField]
    AudioClip[] defaultClickSounds;

    [SerializeField]
    AudioClip[] indexSounds;

    [SerializeField]
    AudioClip[] defaultHitSounds;

    [SerializeField]
    private AudioSource[] prefabs;

    [SerializeField]
    private int poolAmount;
    private Queue<AudioSource>[] sources;

    private Dictionary<string, XAudioComponent> caches = new Dictionary<string, XAudioComponent>();
    private Dictionary<string, bool> loaded = new Dictionary<string, bool>();

    void Awake()
    {
        instance = this;
        DontDestroyOnLoad(gameObject);

        sources = new Queue<AudioSource>[prefabs.Length];
        for (int i = 0; i < prefabs.Length; ++i)
        {
            sources[i] = new Queue<AudioSource>();
            for (int k = 0; k < poolAmount; ++k)
            {
                var audioSource = Instantiate(prefabs[i]);
                audioSource.transform.SetParent(transform, false);

                sources[i].Enqueue(audioSource);
            }
        }
    }

    void Reset()
    {
        prefabs = new AudioSource[System.Enum.GetValues(typeof(XAudioUseType)).Length];
        if (prefabs.Length > 0)
        {
            for (int type=0; type < prefabs.Length; type++)
            {
                GameObject go = new GameObject(((XAudioUseType)type).ToString());
                go.transform.SetParent(transform, false);

                prefabs[type] = go.AddComponent<AudioSource>();
            }
        }
   
        GameObject listenerGo = new GameObject(typeof(AudioListener).Name);
        listenerGo.transform.SetParent(transform, false);

        listener = listenerGo.AddComponent<AudioListener>();
    }

    void Update()
    {
        if (listener && target)
            listener.transform.position = target.transform.position;
    }

    public void Shutdown()
    {
        Clear();

        Destroy(gameObject);
    }

    public AudioSource GetSource(XAudioUseType useType)
    {
        var audio = sources[(int)useType].Dequeue();
        sources[(int)useType].Enqueue(audio);

        return audio;
    }

    public void PlaySound(XAudioUseType useType, XAudioPlayData data)
    {
        var source = GetSource(useType);
        if (source)
        {
            source.gameObject.transform.position = data.position;

            source.clip = data.clip;
            source.pitch = Random.Range(data.pitchMin, data.pitchMax);
            source.volume = data.volume;
            source.loop = data.loop;

            source.Play();
        }
    }

    public void Play(XAudioUseType useType, XAudioComponent audio)
    {
        PlaySound(useType, new XAudioPlayData() { 
            clip = audio.clip, loop = (useType == XAudioUseType.Music ? true : audio.loop)
        });
    }

    public void Play(XAudioUseType useType, string assetPath)
    {
        if (caches.ContainsKey(assetPath))
        {
            Play(useType, caches[assetPath]);
        }
        else
        {
            if (!loaded.ContainsKey(assetPath))
            {
                loaded.Add(assetPath, true);

                XRes.LoadAsync<GameObject>(assetPath, delegate (Object prefab) {
                    loaded.Remove(assetPath);

                    GameObject clipGo = Instantiate(prefab) as GameObject;
                    if (clipGo)
                    {
                        XAudioComponent audio = clipGo.GetComponent<XAudioComponent>();
                        caches.Add(assetPath, audio);

                        Play(useType, audio);
                    }
                });
            }
        }
    }

    public void Stop(XAudioUseType useType)
    {
        Queue<AudioSource> queue = sources[(int)useType];
        foreach(AudioSource source in queue){
            source.Stop();
        }
    }

    public void Clear()
    {
        foreach(KeyValuePair<string, XAudioComponent> it in caches)
        {
            if (it.Value != null)
            {
                GameObject.Destroy(it.Value.gameObject);
            }
        }

        caches.Clear();
    }

    public void Pickup()
    {
        PlaySound(XAudioUseType.Sound2D, new XAudioPlayData() { 
            clip = defaultPickupSounds[Random.Range(0, defaultDropSounds.Length-1)]
        });
    }

    public void Drop()
    {
        PlaySound(XAudioUseType.Sound2D, new XAudioPlayData()
        {
            clip = defaultDropSounds[Random.Range(0, defaultDropSounds.Length - 1)]
        });
    }

    public void Death(bool boss)
    {
        AudioClip[] sounds = boss ? defaultBossDeathSounds : defaultMonsterDeathSounds;
        if (sounds.Length > 0)
        {
            PlaySound(XAudioUseType.Sound2D, new XAudioPlayData()
            {
                clip = sounds[Random.Range(0, sounds.Length - 1)]
            });
        }
    }

    public void Click()
    {
        PlaySound(XAudioUseType.Sound2D, new XAudioPlayData()
        {
            clip = defaultClickSounds[Random.Range(0, defaultClickSounds.Length - 1)]
        });
    }

    public void Index(int index)
    {
        if (indexSounds.Length > 0)
        {
            PlaySound(XAudioUseType.Sound2D, new XAudioPlayData()
            {
                clip = indexSounds[index]
            });
        }
    }

    public void Hit(XAudioUseType useType)
    {
        if (defaultHitSounds.Length > 0)
        {
            PlaySound(useType, new XAudioPlayData()
            {
                clip = defaultHitSounds[Random.Range(0, defaultHitSounds.Length - 1)]
            });
        }
    }
}
